{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# HDBSCAN* Clustering Tutorial\n",
    "\n",
    "This guide will show how to use Tribuo’s HDBSCAN* clustering package to find clusters and outliers using a toy dataset. We'll also look at how to visualize the results and make predictions for new data points. The details of this implementation are described in [this Applied Sciences journal article.](https://doi.org/10.3390/app12052405)\n",
    "\n",
    "## Setup\n",
    "\n",
    "We'll load in some jars and import a few packages. The xchart jar is needed for the plots, and can be downloaded from [Maven Central](https://search.maven.org/artifact/org.knowm.xchart/xchart/3.8.1/jar)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "%jars ./tribuo-clustering-hdbscan-4.3.0-jar-with-dependencies.jar\n",
    "%jars ./xchart-3.8.1.jar"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import org.tribuo.*;\n",
    "import org.tribuo.clustering.*;\n",
    "import org.tribuo.clustering.hdbscan.*;\n",
    "import org.tribuo.data.columnar.*;\n",
    "import org.tribuo.data.columnar.processors.field.DoubleFieldProcessor;\n",
    "import org.tribuo.data.columnar.processors.response.EmptyResponseProcessor;\n",
    "import org.tribuo.data.csv.CSVDataSource;\n",
    "import org.tribuo.math.distance.DistanceType;\n",
    "import org.tribuo.math.neighbour.NeighboursQueryFactoryType;\n",
    "import org.knowm.xchart.*;\n",
    "import org.knowm.xchart.style.markers.*;\n",
    "import java.awt.Color;\n",
    "import java.nio.file.Paths;\n",
    "import java.util.*;"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Declare some methods to help with plotting\n",
    "We'll declare a few methods that are useful for visualizing the results. If you're not really interested in how this is done, just skip down to the next heading."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "// A method to get a new instance of a chart, configured the same way each time\n",
    "XYChart getNewXYChart(String title) {\n",
    "    XYChart chart = new XYChartBuilder().width(600).height(400).title(title).xAxisTitle(\"X\").yAxisTitle(\"Y\").build();\n",
    "    chart.getStyler().setDefaultSeriesRenderStyle(XYSeries.XYSeriesRenderStyle.Scatter);\n",
    "    chart.getStyler().setChartTitleVisible(false);\n",
    "    chart.getStyler().setLegendVisible(false);\n",
    "    chart.getStyler().setMarkerSize(8);\n",
    "    chart.getStyler().setPlotGridHorizontalLinesVisible(false);\n",
    "    chart.getStyler().setPlotGridVerticalLinesVisible(false);\n",
    "    return chart;\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "// A method to add a set of (x,y) points to a chart\n",
    "void addSeriesToChart(XYChart chart, List<Double> xList, List<Double> yList, String seriesName, Color color, Marker marker) {\n",
    "    XYSeries xYseries = chart.addSeries(seriesName,\n",
    "        xList.stream().mapToDouble(Double::doubleValue).toArray(),\n",
    "        yList.stream().mapToDouble(Double::doubleValue).toArray());\n",
    "    xYseries.setMarkerColor(color);\n",
    "    xYseries.setMarker(marker);\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "// A method to multiple sets of (x,y) points to a chart\n",
    "void addAllSeriesToChart(XYChart chart, Map<Integer, List<Double>> mapX, Map<Integer, List<Double>> mapY, Queue<Color> colors) {\n",
    "    for (Map.Entry<Integer, List<Double>> entry : mapX.entrySet()) {\n",
    "        if (entry.getKey() == 0) {    // The Outlier label\n",
    "            List<Double> xList = entry.getValue();\n",
    "            List<Double> yList = mapY.get(0);\n",
    "            addSeriesToChart(chart, xList, yList, \"Points\" + entry.getKey(), Color.darkGray, SeriesMarkers.CIRCLE);\n",
    "        } else {                      // Valid Cluster labels\n",
    "            List<Double> xList = entry.getValue();\n",
    "            List<Double> yList = mapY.get(entry.getKey());\n",
    "            addSeriesToChart(chart, xList, yList, \"Points\" + entry.getKey(), colors.poll(), SeriesMarkers.CIRCLE);\n",
    "        }\n",
    "    }\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "// A method which extracts the (x,y) points from the dataset\n",
    "void setXandYListsFromDataset(List<Double> xList, List<Double> yList, Dataset<ClusterID> dataset) {\n",
    "    for (Example<ClusterID> ex : dataset) {\n",
    "        int i = 0;\n",
    "        for (Feature f : ex) {\n",
    "            if (i == 0)  xList.add(f.getValue());\n",
    "            if (i == 1)  yList.add(f.getValue());\n",
    "            i++;\n",
    "        }\n",
    "    }\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Dataset\n",
    "The toy dataset we'll be using is a small set of 2-dimensional points. A dataset like this with so few samples and only 2 features isn't very realistic, but makes it easy to see how to use Tribuo's HDBSCAN* clustering package.\n",
    "\n",
    "First, we'll load the training data."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "Map<String, FieldProcessor> regexMappingProcessors = new HashMap<>();\n",
    "regexMappingProcessors.put(\"Feature1\", new DoubleFieldProcessor(\"Feature1\"));\n",
    "regexMappingProcessors.put(\"Feature2\", new DoubleFieldProcessor(\"Feature2\"));\n",
    "RowProcessor<ClusterID> rowProcessor = new RowProcessor<>(new EmptyResponseProcessor<>(new ClusteringFactory()), regexMappingProcessors);\n",
    "\n",
    "var csvDataSource = new CSVDataSource<>(Paths.get(\"simple-2d-data/simple-2d-data-train.csv\"), rowProcessor, false);\n",
    "var dataset = new MutableDataset<>(csvDataSource);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, we can look at a plot of the points."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "BufferedImage@3d9dd79d: type = 1 DirectColorModel: rmask=ff0000 gmask=ff00 bmask=ff amask=0 IntegerInterleavedRaster: width = 600 height = 400 #Bands = 3 xOff = 0 yOff = 0 dataOffset[0] 0"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "List<Double> xList = new ArrayList<>();\n",
    "List<Double> yList = new ArrayList<>();\n",
    "setXandYListsFromDataset(xList, yList, dataset);\n",
    "\n",
    "var chart = getNewXYChart(\"Dataset\");\n",
    "addSeriesToChart(chart, xList, yList, \"Points\", Color.blue, SeriesMarkers.CIRCLE);\n",
    "BitmapEncoder.getBufferedImage(chart);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Model Training\n",
    "We'll fit an HDBSCAN* model using a minimum cluster size of 5. This defines the minimum number of points required to form a cluster."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "var trainer = new HdbscanTrainer(5);\n",
    "var model = trainer.train(dataset);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Querying the model, we can see that 4 different cluster labels were identified. The cluster label 0 is the outlier label and indicates that some points in the dataset are marked as outliers or noise. That means the model found 3 distinct clusters and some outlier points."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0, 3, 4, 5]\n"
     ]
    }
   ],
   "source": [
    "var clusterLabels = model.getClusterLabels();\n",
    "var labelsSet = new HashSet<>(clusterLabels);\n",
    "System.out.println(labelsSet);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Using the cluster labels discovered by the model, we can organize the data to make it easy to visualize."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "Map<Integer, List<Double>> mapX = new HashMap<>();\n",
    "Map<Integer, List<Double>> mapY = new HashMap<>();\n",
    "int i = 0;\n",
    "for (Integer label : clusterLabels) {\n",
    "    List<Double> lx = mapX.computeIfAbsent(label, p -> new ArrayList<>());\n",
    "    lx.add(xList.get(i));\n",
    "    List<Double> ly = mapY.computeIfAbsent(label, p -> new ArrayList<>());\n",
    "    ly.add(yList.get(i));\n",
    "    i++;\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, let's see that visualization."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "BufferedImage@3193ef05: type = 1 DirectColorModel: rmask=ff0000 gmask=ff00 bmask=ff amask=0 IntegerInterleavedRaster: width = 600 height = 400 #Bands = 3 xOff = 0 yOff = 0 dataOffset[0] 0"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "// Since there are 3 distinct clusters, we add 3 different colors to a queue to be used in the chart.\n",
    "Queue<Color> colors = new ArrayDeque<>();\n",
    "colors.add(Color.cyan); colors.add(Color.green); colors.add(Color.magenta);\n",
    "\n",
    "chart = getNewXYChart(\"Cluster Result\");\n",
    "addAllSeriesToChart(chart,  mapX,  mapY, colors);\n",
    "BitmapEncoder.getBufferedImage(chart);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Outlier Scores\n",
    "An HDBSCAN* model calculates an outlier score for each point. These are values between 0 and 1, where points with higher outlier scores are more likely to be outliers. For example, we can query the model to see the outlier score for point (1.3, 4.31), which happens to be the point at index 9 in the dataset."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.8449704929196467\n"
     ]
    }
   ],
   "source": [
    "System.out.println(model.getOutlierScores().get(9));"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Looking at the point (1.05, 1.0) which is near the center of the green cluster, we expect a lower outlier score. For the interested reader, this point is at index 4 in the dataset."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.0\n"
     ]
    }
   ],
   "source": [
    "System.out.println(model.getOutlierScores().get(4));"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Predictions\n",
    "Tribuo's HDBSCAN* clustering package conforms to the standard `Trainer` and `Model` interfaces used for the rest of Tribuo, so predictions can be made for new data points.\n",
    "\n",
    "Let's load a few new points."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "CSVDataSource<ClusterID> csvPredictDataSource = new CSVDataSource<>(Paths.get(\"simple-2d-data/simple-2d-data-predict.csv\"), rowProcessor, false);\n",
    "Dataset<ClusterID> predictDataset = new MutableDataset<>(csvPredictDataSource);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The new points are: (4.25, 1.5) (3.9, 4.5) and (1.5, 3.0). We can add those to the existing plot of clusters using a red diamond marker to differentiate them."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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+ully5bp1RZCCCBczgrSQi9xJ4SDBg2qVKnS0qVLe/ToUWII/W6//fb8/Hx71dgAgRchhABC8/HHx6+gvbRuredR7rgQwuXLlyclJQ0YMMCcPnEIR44cOWXKlAULFgwdOjQxMdFMTp48Wc0QQgDhGjFC989eataU7dv1MModF0I4ePBgk7TLL788JSWlTp065nTdunXvvPNOPVfUJZdcYib79++vzieEACLA2UIq6BkuhHD06NHJhapXr27yZv5/ww03mFXdu3c3dZw9e7Y5vW3btjFjxuTk5JjTGRkZ9qQ5R10bIQQQGYEtpIJe4kIIA6mHRps0aWL+aD87NCsry5xOSkoyvaxQoYI53bBhw+zs7CKXJ4QAIshuIRX0GJdDOHXq1NTU1OnTp9t/NAd85o/2E0Tz8vJeeeWVPn36dLSMHDnSWcECQgggstLSqKDXuBzC8BFCAEA4CCEAwNMIIQDA0whhfDt8+PAnn3zChyEAQKkRwnj1+++/z5kz5+abb7Y/FWHw4MFff/21HgIAlIQQxqsZM2b4PxvI1rNnzwMHDug5AMAJEcK49OOPP950000qhMby5cv1KADghAhhXNq1a5duoOW5557TowCAEyKEcWnv3r26gZbp06frUQDACRHCuHTkyJF77rlHVbB169YffPCBHgUAnBAhjFd79uzp0KFDYAhnz56thwAAJSGEcWzfvn0zZswYNmxYenr6jh079GoAQBAIIQDA0wghAMDTCCEAwNMIIQDA0wghAMDTCCEAwNMIIQDA0wghAMDTCCEAwNMIIQDA0wghAMDTCCEAwNPcDGF+fv6rr746a9asd955R68LGiEEAITDzRCOGDHCZ+nVq5deFzRCCAAIh2shXLlyZdWqVZOSkgghAMBF7oQwPz8/JSWlYcOGXbp0IYQAABe5E8Lhw4dXqFDhjTfe6NGjByEEALjIhRC+8847VapU6devnzld6hCODaDO1/9EAACK50IIBw8enJCQ0L59+44dO5533nkmhOeff35qaqqeCw4hBACEw50Q2k8WDXTppZfqueAQQgBAOFwIYaBSPzTqRwgBAOFwOYRTp05NTU2dPn26XhE0QggACIfLIQwfIQQAhIMQAgA8jRACADyNEAIAPI0QAgA8jRACADyNEAIAPI0QAgA8jRACADyNEAIAPI0QAgA8jRACADyNEAIAPI0Q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ai7N0yTtkBzSEwCAsLkWwpdffrlq1apJSUnhPFOmoPweEQIAosOdED711FMVKlSoWbPmvHnz9LoQEUIAQDhcCOH27dvtJ8jUqFEjudD06dP1XHAIIQBEzcGDB5cvXz5u3LgpU6bk5OTo1fHJhRCafZfiMHfuXD0XHEIIANGxb9++++67r1WASZMm6aE45EIII4sQAkB0jBo1KrCCtvXr1+u5eEMIAQAlO3jwYJs2bXQGW7V68skn9Wi8IYQAgJJ98803uoGWAQMG6NF4QwgBAEG5/fbbdQZbtZo8ebKeizeEEAAQlKVLl6oKtm/f/quvvtJz8YYQAgCCcvTo0QULFtx88812Be+6664PPvhAD8UhQggACMFvv/22e/fuvXv36hVxixACADyNEAIAPI0QAgA8jRACADyNEAIAPI0QAgA8jRACADyNEAIAPI0QAgA8jRACADyNEAIAPI0QAgA8jRACADyNEAIAPI0QAgA8jRACADyNEAIAPI0QAoCrfvxRDh/WZyKKCCEAuOeHH+Tqq6VLF1roIndCmJaWlpKSkmwZNGiQXl3o1ltvtWdszZo10xOEEED8sivo8x1baKF73Alh586dGzdu3KBBA5/P17t3b726UIsWLcyA6V+KpV27dnqCEAKIU4EVpIWucieEtm7dugUTwg0bNmRnZ+t1hQghgPjjrCAtdE8chDAxMdH8v379+uPHj/evGhsg4BJlGMLf5LfX5LVH5dGJMvET+USvBoAgFVdBWuiSmA5hhw4dOnfunJqaetVVV9lFXLJkiZqJTgg/lU8vlov936pVpeocmaOHACAYhw5Jx466f/5l4EA9jzIW0yH0y8vLO/vss83wwIED1aoohPCoHL1GrlHfrZWl8m7ZrUcBIBjFtZAKuiG2QjhmzBhz/Lds2TJzeseOHe+//759fm5ubnJyshkePHiwf9gWhRD+Xf7u+G49toyRMXoUAILkbCEVdIk7IRw6dGhKSkrt2rVN284880xzevTo0eb8Jk2amHPS09PN6aysrIoVK15//fXdu3e/5JJLzPmVK1d+99131VVFIYTvyXvOCprlPrlPjwJA8AJbSAXd404I+/btG/gCweTCVxPedNNN5vTUqVMLrCPCHj16NGzYsFq1alWrVr366qtff/11fUVRCeGX8qWzgmZ5QV7QowAQEruFVNBV7oQwgqIQQuMe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",
      "text/plain": [
       "BufferedImage@2b53682e: type = 1 DirectColorModel: rmask=ff0000 gmask=ff00 bmask=ff amask=0 IntegerInterleavedRaster: width = 600 height = 400 #Bands = 3 xOff = 0 yOff = 0 dataOffset[0] 0"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "xList = new ArrayList<>();\n",
    "yList = new ArrayList<>();\n",
    "setXandYListsFromDataset(xList, yList, predictDataset);\n",
    "addSeriesToChart(chart, xList, yList, \"New Points\", Color.red, SeriesMarkers.DIAMOND);\n",
    "BitmapEncoder.getBufferedImage(chart);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, let's make the prediction call."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "var predictions = model.predict(predictDataset);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Using the predicted cluster labels, we add the new points to the existing maps to prepare the updated data for visualization."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "List<Integer> predictionLabels = new ArrayList<>();\n",
    "predictions.forEach(p -> predictionLabels.add(p.getOutput().getID()));\n",
    "\n",
    "i = 0;\n",
    "for (Integer label : predictionLabels) {\n",
    "    List<Double> lx = mapX.computeIfAbsent(label, p -> new ArrayList<>());\n",
    "    lx.add(xList.get(i));\n",
    "    List<Double> ly = mapY.computeIfAbsent(label, p -> new ArrayList<>());\n",
    "    ly.add(yList.get(i));\n",
    "    i++;\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We could have just printed out the cluster labels for the new points, but a visualization is better."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAlgAAAGQCAIAAAD9V4nPAAAs20lEQVR4Xu3dCXgUVbrG8U4QwqqogBhQUdxFuChRxC0qIIKILMIgKMgiN4OCbIIKghgVFALCgCCLKMgqggzKomEJa1hCkuuAOirXO47LFcZd2b97rLrp6XxFSHc66epO/X+ex6eo83VxqE7q7dNdXeXLBQDAw3x6BQAAXkIQAgA8jSAEAHgaQQgA8DSCEADgaQQhAMDTCEIAgKcRhAAATyMIAQCeFgNBOHr06MA/pqWljQYAIGhTp04NzBEl9oLQ/FEAAAiayhGFIAQAlHIEIQDA0whCAICnEYQAAE8jCAEAnkYQAgA8jSAEAHgaQQgAiD3/Etkm8oNeXRQEIQAglnwn0kHEl9ceDDsOCUIAQMkyQfWlXldEJ0SSA1LQbq11VWgIQgBASfmbyI15cXWuyALdH7K1jhS0W6YuDAFBCAAoEV+LnO1IrEW6KjRjHBu02yu6MAQEIQCgRPR3xJVpF+iq0MxxbNBuy3RhCAhCAECJaOKIK7t9qwtD8KVIFccGq4kc1IUhIAgBACXibkdimVZW5DddGJr5IgkBG6wk8o4uCQ1BCAAoEdMcKWhaC11VFHtFBou0FRkq8qnuDBlBCAAoEccdk8JaIvt1lfsIQgBASTkhMluknTURfErke90fFQhCAICnEYQAAE8jCAEAnkYQAgA8jSAEAHgaQQggVu0TWSqyR+So7kHxOCSy09rJ+3VPqUIQAog934ncE/DttGutUETx2i5SN2An9xD5VZeUEgQhgNjT0nG9kktL72HaFd9YF/BUOzlFVxXReuueglVF6os8I3JE94fge5FNYU9YCUIAMSbHcYC221xdiKJLdexe0+KK4xvxC6ztBG62hfW9+1CZ1z1/DtjUDSJ/1yXBcicIu3XrlhSgRYsWuiJoBCHgNW86DtB2e1wXouj+5Ni9dtukC0NzSOQsxzZ9RbpJYW/HRi4u6rsC7gRh48aNfT7fueeem2hp2LChrggaQQh4zWbHEdBuU3Qhim6oY/fa7X90YWh2OjZot1DfdP3SMa2023RdGBQ3gzAjI0N3hI4gBLzGzCoucxwBTxf5hy5E0WVZ90tSO/lWXRWyHY5t2u0/dWEhVjq2YLdQA9XmZhCed955ZjrYpEmTefPm6YrCjA6g1ut/IoBSZ7dI7YDDXxXrFH8Ur6ki5QJ28hVhn5Mi1ouYMx3p5bM+OAzJHscW7PasLgyKO0F40003NWzYsE2bNrVr1zaJWL58+VWrVumi4BCEgDf9YL0X2ldkQnEcoHFSH4qMFXlEZJbI77qziOY50qtZ6CfLHBW53LEdM4XN1YVBcScId+/ebS/s3LmzRo0aJgsHDx6cvyRYBCEAxJa1Ijdad5a/wro302HdH5RdIucEpOBpIpN1SbBcCMKsrCyTf/ZyTk7OhRdeaIKwf//++auCRRACgDcdsCasXawThvfozhC4EIQZGRnVqlXr0qXL8OHDmzZtalIwPj5+yZIlui44BCEAIBwuBGFmZmaTJk0SEhJ8lsTExPHjx+uioBGEAIBwuBCEfunp6evWrdNrQ0QQAgDC4WYQFguCEAAQDoIQAOBpBCEAwNMIQgCApxGEAABPIwgBAJ5GEAIAPI0gBAAU4juRl0X6iKSJfKE7Yx5BCAA4lS35L299usgKXVKI/da1QI/q1dGCIAQAFOgXkTqOGx6dKfK1Ljy5zID7JVW27pwVhQhCAECB1jpS0G6zdeFJ/Lc1fSzCAyOMIAQAFGimI8nsNkoXnsSfHY8yLVFXuY8gBAAUKNORZHZbqgtPorHjUXYL8m3ViCEIAQAFOiHS1JFk9YO7rXwbxwNNKydySBe6jCAEAJzKNyKtApKsichnuuTk5jhS0LR7dZX7CEIAQOE+FnlH5L/06kJ0yZ+Cl4h8pUvcRxACAErQWyK9RdqJjBf5VXdGBYIQKNxPP/1kfrrM/3UHgNhHEAKn8v33348YMSI5T2pq6s8//6yLAMQyghAo0IkTJ/r16+dPQduwYcN0HYBYRhACBdq5c6dKQdvevXt1KYCYRRACBXrzzTd1BlreeecdXQogZhGEQIFWr16tM9CyefNmXQogZhGEQIEOHDjQsmVLlYL33nsvp48CpYmbQZidnT179uxZs2a9++67us+yZMmSWQHmzZunKwhClLD09PTmzZv7U/Cuu+7aunWrLkJp8bX1nfFt0fp1N5QQN4Nw8ODBPkv37t11n6Vx48Z2gS0xMVFXEIQoeV988cX06dNHjhw5c+bMr76KwstioBgcFRkiUibvAiiJIqt1CUot14JwxYoVCQkJlSpVKjQIn3jiiVRLWlqariAIARSHVMclMStaFxWDF7gThNnZ2fXr17/iiivuu+++QoPQROBzzz23bNky3W0hCAGE6fjJ7h/rs26nBy9wJwgHDBhQrly5pUuXdu7cudAg9LvnnntycnLsrtEBAh9CEAII1eeOCLTbzboQpZMLQbh8+fKEhIR+/fqZ5VMH4fDhwydPnjx//vwhQ4bEx8ebykmTJqkaghBAmA6JlHWkoGlddSFKJxeCcNCgQSbSrrnmmqSkpBo1apjlmjVr3nfffbouvyuvvNJU9u3bV60nCAGE70+OFDRtla5C6eRCEI4cOTIxT+XKlU28mf/feuutpqtTp04mHWfPnm2Wt2/fPmrUqJ07d5rllStX2pVmjdoaQQggfN+JJAVEYLzICF2CUsuFIAyk3hqtX7+++aN9dmhGRoZZTkhIMHlZpkwZs1y3bt3MzMx8jycIARSToyLzRPqLPCOSqTtRmrkchFOnTk1JSZk+fbr9RzPhM3+0TxDNzs5+9dVXe/Xq1cYyfPhwZwrmEoQAgPC4HIThIwgBAOEgCAEAnkYQAgA8jSCMbUePHv3000+5GQIAFBlBGKuOHTs2Z86cZs2a2XdFGDRo0DfffKOLAACFIQhj1YwZM/z3BrJ17dr10KFDug4AcEoEYUz66aef7rjjDhWExvLly3UpAOCUCMKYtGfPHp2BlrFjx+pSAMApEYQxaf/+/ToDLdOnT9elAIBTIghj0vHjxx944AGVgrfddtuHH36oSwEAp0QQxqp9+/a1bt06MAhnz56tiwAAhSEIY9iBAwdmzJgxdOjQtLS0Xbt26W4AQBAIQgCApxGEAABPIwgBAJ5GEAIAPI0gBAB4GkEIAPA0ghAA4GkEIQDA0whCAICnEYQAAE8jCAEAnkYQAgA8zc0gzMnJee2112bNmvXuu+/qvqARhACAcLgZhE899ZTP0r17d90XNIIQABAO14Jw1apVFStWTEhIIAgBAC5yJwhzcnKSkpLq1q3bvn17ghAA4CJ3gnDYsGFlypRZuHBh586dCUIAgItcCMJ33323QoUKffr0MctFDsLRAdR6/U8EAKBgLgThoEGD4uLiWrZs2aZNm4suusgE4cUXX5ySkqLrgkMQAgDC4U4Q2ieLBrrqqqt0XXAIQgBAOFwIwkBFfmvUjyAEAITD5SCcOnVqSkrK9OnTdUfQCEIAQDhcDsLwEYQAgHAQhAAATyMIAQCeRhACADyNIAQAeBpBCADwNIIQAOBpBCEAwNMIQgCApxGEAABPIwgBAJ5GEAIAPI0gBAB4GkEIAPA0ghAA4GkEIQDA0whCAICnEYQAAE8jCAEAnkYQAgA8jSAEAHgaQQgA8DSCEADgaQQhAMDTCEIAgKe5E4RZWVmLFi2aZdm4caPuzrN+/frVAd5//31dQRACAMLjThBedtllvjzx8fEPPvigrrA0btzYX2YkJibqCoIQABAed4IwJSXlueeemzlzZocOHeyQW7NmjS7KC8KHHnooxTJ06FBdQRACAMLjThD6rVy50p4Url+/XvflBeH06dNP8Q4qQQgACIdrQfj444/fc88955xzTpUqVYYPH667LYFvjZYpU6Znz57+rtEBAh5BEAIAQuNaELZt2zYxMdHEmwm5u+++Ozs7W1fk5j7yyCMmI6dOndqtWzc7Dl999VVVQxACAMLhWhDaVq5cmZCQYBJu2rRpui8/+/yavn37qvUEIQAgHC4EYUZGxurVq+3ljRs3VqpUySTciy++mGtNAdu0abNgwQKznJmZOWXKFLtsw4YNVatWNWXON1EJQgBAOFwIQpNzcXFxderUufbaaytUqGDi7ZxzztmyZYvpql+/vvljWlparpWXZrl69eqNGjWyw7JmzZqbNm1SWyMIAQDhcCEITcL17t375ptvTkpKuummm/r06WMmfHZX4IwwKysrNTW1Xbt2pqxx48a9evU66ZmlBCEAIBwuBGHxIggBAOEgCAEAnkYQAgA8jSAEAHgaQQgA8DSCEADgaQQhAMDTCEIAgKcRhACC8LZIE5GqIg1FJomc0P1A7CIIARQmTcSXv/XQJUDsIggBnNI3IuUdQWjaZl0IxCiCEPCkf4jMFhkjsqaw9zmXOyLQbqm6EIhRBCHgPW+IVA6ItGSRA7rk35Y5ItBuz+pCIEYRhIDH5IqUc6RaJ131b1+LJDjqTduoC4EYRRACHjPQEWmmxYn8oAv/7UVH/QO6BIhdBCHgMfc4Us1uu3RhPotEGolUEKkv8pLIcd0PxC6CEPCYxx0RaFq8yE+6EPAIghDwmI9FKjqCkO8FwiUHDx58/vnn27dv36ZNm5EjR3711Ve6ouQRhEDY/m595fwRkVdFftad0Wi5yNkBKXivyI+6pHAfiDwlMlTkPd0DBOn7779v165dcoCWLVt++eWXuq6EEYRAeObln2BdKPI3XRKNDoq8biXZDt1TuOPWyTKBE0oTpUd0FVCol156KTAFbU8++aSuK2EEIRCGv5/sbcaGIsd0YXT5SOSGvNFWsE4KDenkl5cd/2TTntFVQKG6du2qYzA5uVWrVidOnPoqD8WMIATCMNaRB3bL0YVR5HuRWo4Bj9VVp3Kd4+GmXaKrgEKdNAjvvvtugjA0BCHclOLIA7u9owujSKpjtD5rXnhYFxaohuPhPutL9xE9dqE0GDdunI7B2Hpr9IknnsjIyNBrI44ghJumOvLAbp/rwijS1jFau+3RhQW63fFY067RVUChvv/++/bt2wemYKtWrf75z3/quhJW9CBMSko6++yz09LSdEdkEYRw0w8i5zkioaOuii59HAO2W/AHnzXWlWjUwxfrKiAY//rXv1544YWOHTu2bdt21KhRX3/9ta4oeUUPwubNm/ssZmHjxo26+5RSU1PNv9lE6Y033tizZ8+1a9fqiqARhGH5SORJkT+JjBTZrzsRlGyRegF50MH6EK64HBL5i0gXkUdF/qo7Q/CrdTfd+0X6FfDW6HX6EYWYIVIl77EVRCbofiCGFD0I9+zZM2DAgISEBJOFVatW7dWrV0oeXepw8cUXV6tW7YILLihbtqx5+DnnnLNr1y5dFByCsOgW5r/PXBW+EFZUh0V2iyyxvqtejL4VuTJ/XHXXJUH50jqTJXA7DfL/sWaRvvJh8n6tyLvWNzGAWFb0ILStXLny7LPPtqeGfrrIYfXq1fbC/Pnz7Ye88847+UuCRRAWkTk4np7/aGjaOcU6m0GYOjqeIJ91B6VQtXJsxGe9E/CwSGuRp0kyeF1YQbhs2bIGDRrYSVavXr2kPLruZDZs2GDicOjQoeaxtWvXZkYYaa85jox2W6EL4Y4jBdwXvo0uLMQvImUcG/FZb5MCsBQ9CE2G2W9sVqtWbcKECbq7MLfeequdoFWrVn3jjTd0d2FGB1Dr9T8RJ/Wc48hot2m6EO74xvHU2C3UD/M+c2zBbsm6EPCsogehmfmZGLv77rs3b96s+4IwY8aMkSNHNm3a1GykcuXK6enpuiI4BGERvec4MtotUxfCNYmOZ8dnvZ8ZkhMiZzo2YtoAXQh4VtGDsGXLlpMnT9ZrQ2dmhCYLx4wZozuCQxAW0XGRGx0Hx1a6Cm6a4XiCKol8qqsKN9Gxnaoi/9BVgGcVPQizs7P1quAsX768S5cuJkRnzZr16KOPxsXFmSCcM2eOrgsOQVh031jnSvgPjp05U6bELBHpJNJcZIhISN+SmhhwLdOLRDbp/qCcsK6glpC3ncuY9wP5FD0Ii+ytt96yPx20lSlTpkePHrooaARhuP5XZCPnDZaknvlnY2eFeCXS30V2iuzVq0P2m3WjieL9ggdQKrgQhMbmzZvNdPCll14y/y/aR4x+BCGi2nLH25I+6/YUKJX+R2SESDuRgaFcsg5ucycIixFBiCI6Zl0UZmUolxYrAjUd9LdI33k0CP+09kZ21N9DKmqtEzkj4CkuKzJdlyA6EYTwpCyRqwKOWb2s79uVhIKucB3Su6Ml7VfrZFT/2K60LpSDkPx6sqvOlhf5RBciChGE8J6DIuc6jll9dFXxeN7xF5lWWeSoLnST82ZSNUUO6CqcynrHPrQbV2GNBQQhvGec42jls26n8J0uLAY/ilzg+Lte1lVuOniyW0n4QrxVLxY4dqDdhupCRCGCEN7zoONoZbd1urB47Be5M++vOEtkiu532QbHfrBbF12IU/nYsQPttkgXIgoRhPCe4Y6jld3+rguL00/ReqOrzx37wW5P6EIU4gHHPrzGumYsoh5BCO/Jts7oU8es66wvnntTY8feOM06nwgh+UXkPwPeZ24RlecG42QIQnjS1PxZeJG3v2lupsJ1A/ZGWetuwCiaH0W2lMznzSgxBCG86r+se7Wbl/CviPysOz3nF2s/mL3xrEiu7gRKN4IQAOBpBCEAwNMIQsBtP4o8Yt0Uorp1JZp9uh9AiSIIAVf9KnJ5/jM2y3OFMyCiCELAVU/nT0G7NdZVAEoOQQi46hZHCppWpsQuAg7AgSAEXHWzIwVNi+cbHUDkEISAq55ypKBpjXQVgJJDEAIlI8jb2/6U/6ouppUTydRVAEoOQQgUt3SRJOtynWdad7oo9Gpb/7LuDFzbur/5ndalUAFEEEEIFKuVjtv7XcKZL0BUIwiBYlXH8YGfz7qoKYBoRRACxecfjgi0WwtdCCB6EIRA8fnOEYF2u1cXAogeBCFQrBo4UtC0yboKQPRwLQi3bt26cuXKVatW7dmzR/eFgiBEdMkUqZg/BW8K+qsUp/aDyPd6Xcn6JYhTXoHY50IQZmVlXX755b48Z5xxxvPPP6+LLI0bN/aXGYmJibqCIEQU+kSks3Up7UbWaTJHdH/IdlibsmP1apFNur/4fSxyW97prxeJvKP7gdLEhSDcuXNntWrVevXqlZqaakdd2bJl169fr+vygrBFixZtLN27d9cVBCFKvb3W/SgCp5iniezUVcXpG5Fqjnd3V+gqoNRwIQizs7MzMzPt5YyMDHu2N2/evPxVf7CDcNmyZatXrzbzSN1tIQhRyrV1ZJJpd+iq4tTP8df5rG9DAqWUC0EYaPLkyT7r3dHt27frvvxvjVauXHnYsGH+rtEBAh5BEKLUSXRkkmlVRE7owmJzveOv81lvkx7UhUDp4GYQvv7661WqVClbtuyUKVN0n6Vr1669e/d+9tlnW7RoYcfh3LlzVQ1BiFLuSkcmmVZLVxWnOx1/nc+6AuohXQiUDq4FYVpaWkJCQqVKlaZNm6b7TqZu3bomCB999FG1niBEKTfIkUmm9dZVxWmi46/zcU0AlGbuBOGwYcPi4+OrV6++ePHiwPWjRo1KSUlZtmyZWd6xY8eSJUvs9ZmZmabYBOHQoUMD63MJQpR6v4jUz59Jl1jX6S45x0Ruz/831hT5XFcBpYYLQbh9+3b/J39+L7/8sumqX7++WTaTxdy882guv/zyFi1a1KxZ02d9lJienq62RhCi9Dsk8pxIcyufRkXkEt7HrIsAtLa+BDlE5IDuB0oTF4Jw586dSQ6vv/666erUqZNZnj17tlnevXt3v379mjRpcsEFF9SpU6d169YrV67U2yIIAQDhcSEIixdBCAAIB0EIAPA0ghAA4GkEIQDA0whCAICnEYQAAE8jCAEAnkYQAgA8jSAEAHgaQQgA8DSCEADgaQQhAMDTCEIAgKcRhAAATyMIAQCeRhACADyNIAQAeBpBCADwNIIQAOBpBCEAwNMIQgCApxGE0e6E+Q8AUGIIwuiVLunXyXVlpexZctZD8tBBOagrAABhIwij1ApZESdxPvH522Vy2W/ym64DAITHzSBcu3bt+++/r9eGqLQG4flyfmAK2u0FeUHXAQDC40IQZmVlXXPNNeXKlfNZqlevPmHCBF0UtFIZhF/IF84UNK2VtNKlAIDwuBCEO3fuPP300zt16jRs2LCGDRuaLExISMjIyNB1wSmVQfiNfONMQdPaSTtdCgAIjwtBmJ2dvXnzZnt5/fr19rxw3rx5+auCVSqD0Kgn9ZxBOEWm6DoAQHhcCMJA06ZNMylYuXLlrVu36r7glNYg3CbbKkiFwBS8WW4+Jsd0HQAgPG4G4cKFC88888z4+Pjx48frvsKMDqDW639izPpIPuoknS6VSxtJo1RJPSJHdAUAIGyuBeErr7xSsWLFhISEcM6UyS29M0IAQGS4E4TPPvtsmTJlqlatOnfuXN0XIoIQABAOF4Jwx44d9gkyVapUScwzffp0XRccghAAIubw4cPLly8fM2bM5MmTd+7cqbtjkwtBaPZdksPrr7+u64JDEAJAZBw4cKBbt27JASZOnKiLYpALQVi8CEIAiIwRI0YEpqBtw4YNui7WEIQAgMIdPny4adOmOgaTk5955hldGmsIQgBA4b799ludgZZ+/frp0lhDEAIAgnLPPffoGExOnjRpkq6LNQQhACAoS5cuVSnYsmXLr7/+WtfFGoIQABCUEydOzJ8/v1mzZnYK3n///R9++KEuikEEIQAgBL///vvevXv379+vO2IWQQgA8DSCEADgaQQhAMDTCEIAgKcRhAAATyMIAQCeRhACADyNIAQAeBpBCADwNIIQAOBpBCEAwNMIQgCApxGEAABPIwgBAJ5GEAIAPI0gBAB4GkEIAPA0ghAA4GkEIQDA09wJwtTU1KSkpETLgAEDdHeeVq1a2TW2hg0b6gqCEEDM+uyzzwYOHHjnnXeaY92IESO+/fZbXYGIcCcI27VrV69evTp16vh8vp49e+ruPI0bNzYFJv+SLC1atNAVBCGA2PTpp582a9YsOUCbNm0OHjyo61Dy3AlCW8eOHYMJwo0bN2ZmZuq+PAQhgFg0YMCAwBS0jRs3Tteh5MVAEMbHx5v/165d2/yI+LtGBwh4RAkG4e/y+xvyxkAZOEEmfCqf6m4ACNqxY8fuvPNOHYPJyd26ddOlKHlRHYStW7du165dSkrK9ddfbyfikiVLVE1kgvAz+ewKucInPrtVlIpzZI4uAoDgnDhxomXLljoGk5PN8VCXouRFdRD6ZWdnn3/++aa4f//+qisCQXhCTtwoN/pT0G7lpfxe2atLASA4Tz75pI7B5OQpU6boOpS86ArCUaNGmfnfsmXLzPKuXbs++OADe31WVlZiYqIpHjRokL/YFoEg/Lv8XaWg3UbJKF0KAMH56quvWrduHZiCXbp0+fXXX3UdSp47QThkyJCkpKTq1aubbDv33HPN8siRI836+vXrmzVpaWlmOSMjo2zZsrfcckunTp2uvPJKs758+fLvvfee2lQEgvB9ed+ZgqZ1k266FACCduDAgbFjx3br1q1Hjx5mLkgKusWdIOzdu3fgFwQT875NeMcdd5jlqVOn5lozws6dO9etW7dSpUoVK1a84YYb3nzzTb2hiAThV/KVMwVNe0le0qUAgFjjThAWowgEofGAPKBSsIbU+F/5X10HAIg1BGFQfpQfO0pHfwrWlbqZkqmLAAAxiCAMwUfy0UJZuFW2/i6/6z4AQGwiCAEAnkYQAgA8jSAEAHgaQQgA8DSCEADgaQRhpO2X/QNlYDNp1kt6pUu67g7CJtnUR/rcIXf0l/4fy8e6GwAQCoIwotbJuipSxf99xDiJS5VUXXRKE2VivMT7t1BBKqyUlboI+e2VvQtkgXkB8Zv8pvsAeB5BGDmH5NB5cp66Qk0ZKbNbduvSAuyTfeWknNpCDanxo/yoS2FRV0K4UC7cIlt0EQBvIwgjZ5tsUxlmt9ES7JgnyATnw01bK2t1KSwPyoNqX1WTat/IN7oOgIcRhJHzV/mrM8NM6yf9dGkBnpKnnA83bb7M16UQ+Va+de4r08bIGF0KwMMIwsj5Sr4K/HjP396QN3RpAZbLcufD4yTuE/lEl0IkXdKdu8u0B+QBXQrAwwjCiBogA9RBuaE0PCJHdF0BjsvxG+VGtYVe0kvXwbJf9jtT0LSn5WldCsDDCMKIMpk3QkZUkAr2Ebm9tP9avtZFp3RADtwv99szywRJGCyDuQL4Kdwqt6oUNDvtb/I3XQfAwwhCFxyTY/tk38/ys+4I2q/yq9lC8FNJzzKTwnpSz5+CFaXiHJmjiwB4G0FY+v1g/vOww3J4vswfIkMmysTP5XPdDcDzCMJS67gcHy/jEyXRzITOkrNMEvAmKgA4EYSl1kAZqD4eay2tdREAeB5BWDp9Lp+f9Ksa78v7uhQAvI0gLJ0WyAJnCvpCuYoNAHgEQVg6rZAVzhQ0bbyM16UA4G0EYen0nXwXeJsLu5WRMtmSrUsBwNsIwlLrNXlNfUw4XIbrIgDwPHeC8MUXX0xOTq5Vq1ZiYuKAAQN0dyiKNwh3yI6JMnGWzPpMPtN9YftJflomy8bK2BWy4pAc0t0lYLNsbiftLpfL75F7lspS3Q0AcCsI77777ksuucQEoc/n69mzp+4ORXEF4WE53FW6+idP5aRcmqTpojCYiK0jdfzbv0Ku2Ct7dREAIOLcCcKcnBzz/44dO0ZPEA6X4eoTtTiJWyNrdF2R/Cg/XiAXqO1fLVeb9NWlAIDIcicIbVEVhNWlugoq09pJO11XJAtloXPjpqVLui4FAERWrAbh6ABqvf4nBuGgHHSmlGn1pJ4uLZLRMtq5cdNekVd0acF+kB9SJKWG1Cgn5a6T6/hqPAAUi1gNQr9iCUIpYEbYXtrruiJZJIucGzdtnazTpQU4LIcbSIPAx8ZJHOe/AED4CML/N1JGqpQySfOBfKDriuRn+flCuVBt3wRb8J8RTpSJ6uGmnSvnnpATuhQAEAp3gnDIkCFJSUnVq1c3QXjuueea5ZEjR+qi4BRXEB6RIw/JQ/6MKS/lJ8kkXRSG3bK7rtT1b/9qufoj+UgXFayDdHAGoWkfy8e6FAAQCneCsHfv3on5FfnbhMUVhLYcyfmL/GWuzP1v+W/dF7bf5LcVsmKCTHhP3gt+LmjrLJ2dKWjaftmvSwEAoXAnCItR8QZh1JopM50paKaYug4AECKCMDYcl+O3yW2BKVhOyq2X9boOABAigjBmHJEjz8lz18q1F8qF7aV9juToCgBA6AhCAICnEYQAAE8jCAF4wvHjx9esWZOWljZp0qQtW7bobngYQQig9Pvtt9/69u2bHGDUqFEnTnA9CvyBIPx/R+XoAlnwmDw2RsbskT26G0AsM7PAwBS0vf3227oOnkQQ/uFb+TZJkvzfTDhNTntBXtBFRfKZfPapfKrXAoistm3b6hhMTu7Xr5+ugycRhH9wXsAsTuLC/Jbeall9vpxvb6221F4pK3UFgIg4cuTI7bffrmMwOblz5866FJ5EEMov8ksZKaOC0LSe0lOXBm2H7DDTysCtmb9is2zWdQAi4qGHHtIxmJw8YsQIXQdPIghln+xzpqBpTaWpLg3anXKnc4PJkqzrAETE+vXrVQo2bdr044+5Zj3+QBD+cZpMBangzK3+0l+XBq2aVHNusIpU0XUAImXFihV33XWXnYLt2rXjGxTwIwj/8JQ8pUKrklQK5ySXK+VKZxByjWzAXYcOHdq3b98nn3xy9OhR3QcPIwj/cFgOPyqPxkmcnVi1pNYaWaOLQvGEPOEMwoEyUNcBANxGEP7bF/LF2/L2Jtn0i/yi+0L0u/x+g9wQmIJJkhT+ZgEAxY4gLCnH5Ng0mdZBOrSX9lNkylHhrRgAiEYEIQDA0whCAICnEYQAAE8jCAEAnkYQAgA8jSAEAHgaQQgA8DSCEADgaQQhAMDTSlsQjh8/fjQAAEEbN25cYI4oMRCEAACUHIIQAOBpBCEAwNMIQgCAp8VeEOrPQAEAKIzOkgCxF4RR6NS7OBpE/whzY2GQjLBYRP8go3+EubEwyOgfoR9BWAyi//mO/hHmxsIgGWGxiP5BRv8Ic2NhkNE/Qj+CsBhE//Md/SPMjYVBMsJiEf2DjP4R5sbCIKN/hH4EIQDA0whCAICnEYQAAE8jCMOSkZExadKkfv36paSkTJ48WXdH3OLFi81g2lhefPFF3Z1nyJAhdo2ta9euuiKCsrKyzD7s3bu3GUmHDh2ef/757OxsXRRBa9asefjhh5s0aXLFFVdcf/31jz322O7du3VRbq4ZZOA+NJ577jldFEEbNmzo2LFjUlLSpZdeet111/Xt23fHjh26KIJWrlx577332uMxO9P8yJ30aV29erXajTNnztRFEZeTkzN48GDzSx0ln3KZ8XTv3t3snB49eug+y6uvvqp249q1a3VRBL311ltqPAsXLtRF0YQgDMv999/vy3Pffffp7ohr27atGUnFihXN/x988EHdnefmm2/2D9uoXr26roig5cuXmzGcdtppZ555pj2eli1b6qIIevnll80YLrvssv/4j/+Ii4szy+3atdNFubl79uwJ2IV/MDmkiyJo6dKl5nlv0KBB/fr17WGbHwZdFEFTp06tXLnyNddcY15P2PvH5Iouys1dtGhRvp3o8w0fPlwXRdyIESPswTRs2FD3uWHYsGH2eM4//3zdZ3niiSf+vQctS5Ys0UURZJ59NZ4JEyboomhCEIZl7ty5b7zxRpcuXXzREYTvvffe1q1b7fEUGoTp6em6ww1mBmZ+jTMzM83yoEGDzMDMcXzz5s26LlKWLVv29ttv28uPPPKIGc8ZZ5yRv+QPdhBeddVVusMlu3bt2rlzp73cq1cvMzaTQPlLIsrMR80uspdbt25txnPbbbflL/mDHYTuZrZiJqmVKlU677zzfNERhGZuXaFCBfMSx1dYED7zzDO6wyV2EJqfQ90RrQjCYmAfd6IhCG1BBuG1116blJRkpjvmN01XuMS8sPBZs8Pt27frPjc89dRTZjx169bVHXlBaDLS7MPrrrvuz3/+s53lLsrJyTEH8ddff908s2Zsffv21RWRlZWVZcYzc+ZMswPNeJ599lldkReENWvWNLvxxhtvNNPBk76DGjFmH15//fUXXXTRqFGjoiEIzd4ws+pLL730L3/5S6FBePHFF5vdePvtt5sc0hWRZQeheTFhxnPLLbe4+6lBMAjCYhCLQWgfek4//XT7aP7+++/roogzcxqTKGY8f/rTn3SfG5YvX161atX4+PiXX35Z91lBaLrMEcq8VC9TpowZduPGjXVRZG3bts2XxxzBt2zZoisiy8yt/eMx00H/hDWQCcKEhAQzsfa/g3qKn9sIMC99zLM5f/78F154wd6NuiKyHn/88bJlyy5evPjVV1/1nTIIK1eubEZrT2Tj4uLS0tJ0UQSZIDSz2Kuvvtpks/20Dhw4UBdFE4KwGMRcEK5bt85e2LRpU7Vq1UzxoEGD8pdEmjlqN2rUyIykefPm/rfUXGTmVSYFzTGxoBezZuqwfv16e/mVV16xf9vdnVub2cOsWbPMVKZevXpmMOZVha6IrB07dpjpoJnk1alTx4yndevWuiI3d7vFXh4wYIApMwdQtyaFH3zwQcWKFe039KIhCDMyMsyrhI4dO5qJ9fPPP2/Gk5iYaJad+8dU2r815sfSzAh9br8s27p1q/91jzkQmfHUqlUrf0l0IQiLQfQHoXlFaQ6RGzZsyLV+VQx/l33Q7NOnj39N5Jn5qP3K0Yzc+UseeebVtDkAVapUadq0af6V5kAzy2KPMHCc6enpdhAuWLDAv9JF9uzBTFijYWfmWlcYMeOpUaNGrnWINPvwzTfftLsCR2i/njCzmZOephsB5tWP/Twqbr0yM6+r9FAsJrDXrFljduM777xjVwbuRvO7bGrM77V/TeQFjic1NdVXwAft0YMgDMuiRYvM03zrrbeaZzopKcksz507VxdFkDmUpKSkXH311WY8DRo0MMv2odz+0Gjs2LG51pHokksuMVPAyZMnd+/e3f7VcvGc9Y0bN5pDpD3g1DwuniwzZ84cEyH2ocR/8ndWVpaZs9r7yp7BmBnDbbfdZg7xEyZMuP766836M88886Tv/kXG4MGDe/fuPW7cuDFjxpz6xIrIMK8OH330UfOSwuwi+zNCe4b62muvmeXatWvbZQ8//PC9995rhm32p13m4iTMBI//Gbd/Zc4++2yz7NbriU2bNqXksU84qlq1qlnetm1b//79fQGTbPOj2KNHj0mTJg0dOtREjukyv9r5NxZRZhbbqVMn+9k3s1gznqZNm+qiaEIQhsWeCwZyd15ozwUD2fPCwCA0B+uLLrrIX1C+fPkBAwboDUXQ0qVL/z3cPC6e/G1+e/VofD6z01QQzpgx46yzzvIXmN92k6B6WxFkn+DqZ57i+fPn66II6tChQ+B4zKsK+31jFYQjRoywv+1ju+qqq9599918G3JJNLw1Gkh9RqiCsH379qeddpq9D82Uunnz5u6eumV+GhMSEvxPq5kk+D9HiE4EYVhWrVplv13m99e//lUXRZB5Cbk6P3tqtW7dOrMc+LuxYcOGN998c968ee7+whhmAGofGi6OaseOHWofGjk5OWZa4F/2F5s/mtG+9dZbgSvdYp7rBQsWmPG89957us8NGRkZ5mfMjCfwizq7du0yO+2DDz7wr9m9e7f5rTFl7n7CqpgDtxnS4sWLdYdLzEuxwP22detW80ezh/0FZppoXlOaMW/cuNG/0kVmwMuXLzfjWbNmje6LPgQhAMDTCEIAgKcRhAAATyMIAQCeRhACADyNIAQAeBpBCADwNIIQAOBpBCEAwNMIQiCG7d69u1mzZklJSf7b7ixevNj8sXHjxv57jAA4NYIQiG3jxo3zWZdjTk9PN7l46aWX+ty+nQgQWwhCIObdddddJvxuueUW+yrwl19+eVZWli4CUACCEIh5mzZtql69un2l/3Llyi1dulRXACgYQQiUBkOHDrWDsE2bNroPwCkRhEDM27ZtW61atewgLF++fFTdzwiIfgQhEPPsu+A2atSoU6dOZqFBgwZu3VQdiEUEIRDbpk2bFhcXV6lSpVWrVu3YseP88883WfjYY4/pOgAFIAiBGLZly5YaNWqY5Hv66aftNXPnzo2Pjy9Xrtzbb7+dvxbAyRGEAABPIwgBAJ5GEAIAPI0gBAB4GkEIAPA0ghAA4GkEIQDA0whCAICnEYQAAE8jCAEAnvZ/fDoiu99dl3UAAAAASUVORK5C",
      "text/plain": [
       "BufferedImage@57a77532: type = 1 DirectColorModel: rmask=ff0000 gmask=ff00 bmask=ff amask=0 IntegerInterleavedRaster: width = 600 height = 400 #Bands = 3 xOff = 0 yOff = 0 dataOffset[0] 0"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "// Again, we want to set the correct colors to be used in the chart.\n",
    "colors.add(Color.cyan); colors.add(Color.green); colors.add(Color.magenta);\n",
    "chart = getNewXYChart(\"Prediction Result\");\n",
    "addAllSeriesToChart(chart,  mapX,  mapY, colors);\n",
    "BitmapEncoder.getBufferedImage(chart);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The point (4.25, 1.5) is marked as an outlier. The point (1.5, 3.0) is shown to belong to the magenta colored cluster in the middle. Let's check the predicted outlier scores for these two points."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The outlier score for (4.25, 1.5) is : 0.8492520788446892\n",
      "The outlier score for (1.5, 3.0) is : 0.02241334109160653\n"
     ]
    }
   ],
   "source": [
    "System.out.println(\"The outlier score for (4.25, 1.5) is : \" + predictions.get(0).getOutput().getScore());\n",
    "System.out.println(\"The outlier score for (1.5, 3.0) is : \" + predictions.get(2).getOutput().getScore());"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Optimizing Training Performance\n",
    "Tribuo's HDBSCAN* package supports multi-threading for the core distance calculations step of the algorithm. For larger datasets, it is certainly worthwhile to train the model with multiple threads. \n",
    "\n",
    "Additionally the core distance calculations, which are just nearest neighbour queries, can be done using different techniques. The default technique uses a brute-force algorithm, which calculates the distances between all points and tracks the `k` smallest distances. The other technique uses a k-d tree algorithm, which may reduce the time to make nearest neighbour queries for some datasets. Further, the k-d tree algorithm may use more memory when training a model compared with the brute-force algorithm. \n",
    "\n",
    "This is how to instantiate a trainer to use 4 threads, and the brute-force algorithm. To use the k-d tree algorithm instead, the fifth parameter would be `NeighboursQueryFactoryType.KD_TREE`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [],
   "source": [
    "var trainer = new HdbscanTrainer(5,    // The minimum cluster size\n",
    "                                 DistanceType.L2.getDistance(),  // The distance function \n",
    "                                 5,    // The number of neighbors to use to calculate the core-distance\n",
    "                                 4,    // The number of compute threads\n",
    "                                 NeighboursQueryFactoryType.BRUTE_FORCE  // The nearest neighbour query algorithm\n",
    "                                );"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Conclusion\n",
    "We looked at Tribuo's HDBSCAN\\* implementation and saw how to train a model, then make predictions with that model. HDBSCAN\\* stands for hierarchical density-based spatial clustering of applications with noise and yes, the original authors of the algorithm have used an asterisk in the name.\n",
    "\n",
    "We plan to further expand Tribuo's clustering functionality to incorporate other algorithms in the future. If you want to help, or have specific algorithmic requirements, file an issue on our [github page](https://github.com/oracle/tribuo)."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Java",
   "language": "java",
   "name": "java"
  },
  "language_info": {
   "codemirror_mode": "java",
   "file_extension": ".jshell",
   "mimetype": "text/x-java-source",
   "name": "Java",
   "pygments_lexer": "java",
   "version": "12+33"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
